package zm.irc.client;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;

import zm.irc.RunIrcClientBackEnd;
import zm.irc.cfg.IrcConfig;
import zm.irc.ioc.ApplicationContext;
import zm.irc.ioc.annotation.AutoWired;
import zm.irc.ioc.annotation.Service;
import zm.irc.message.receive.IrcReceiveMessage;
import zm.irc.message.send.IrcJoinMessage;
import zm.irc.message.send.IrcLogonSaslMessage;
import zm.irc.message.send.IrcSendMessage;
import zm.irc.msgqueue.LocalMemoryMsgQueue;
import zm.irc.threads.IrcThread;
import zm.irc.threads.MsgSendThread;


@Service(isSinglet = true)
public class IrcClient {
    private static final Logger log = Logger.getLogger(IrcClient.class);
    public static final int DEFAULT_PORT = 6667;


    @AutoWired
    private IrcConfig ircConfig;
    @AutoWired
    private ApplicationContext context;


    /**
     * key : channel  name
     * value : channel
     */
    private Map<String,IrcChannel> channels;
    private LinkedList<IrcChannel> channelList;

    private volatile IrcChannel currentChannel;
    private int currentChannelIndex; // For internal Use only;

    private  Socket socket;
    private boolean shouldPrintChatMsg;
    private volatile BufferedWriter writer;
    private  volatile BufferedReader reader;
    @AutoWired
    private LocalMemoryMsgQueue localMemoryMsgQueue ;



    public IrcClient() throws Exception{
        this.channels = new HashMap<>();
        this.channelList = new LinkedList<>();
        this.currentChannelIndex = 0;
        this.shouldPrintChatMsg = true;
        
    }

    public String getNickName(){
        return this.ircConfig.getNickName();
    }
    public String getServer(){
        return this.ircConfig.getServer();
    }

    public int getPort(){
        return this.ircConfig.getPort();
    }

    public boolean isShouldPrintChatMsg() {
        return shouldPrintChatMsg;
    }

    public void setShouldPrintChatMsg(boolean shouldPrintChatMsg) {
        this.shouldPrintChatMsg = shouldPrintChatMsg;
    }

    public void switchChannel(String channelName){
        IrcChannel c = this.channels.get(channelName);
        if(c !=null){
            this.currentChannel = c;
        }
    }

    /**
     * key : channel  name
     * value : channel
     */
    public Map<String,IrcChannel> getAllChannel(){
        return this.channels;
    }

    public IrcChannel getChannel(String channelName){
        return this.channels.get(channelName);
    }

  

    public IrcChannel getCurrentChannel(){
        return this.currentChannel;
    }

    private void init()throws IOException{
        this.socket = new Socket(this.ircConfig.getServer(), this.ircConfig.getPort());
        if(this.writer != null){
            this.writer.close();
        }
        if(this.reader != null){
            this.reader.close();
        }
        this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
        this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
    }

    public boolean reconnect(){
        try{
            this.init();
            this.channels.clear();
            log.info("Nick Name :" + ircConfig.getNickName() + " Channel List ：" + this.ircConfig.getChannels());
        this.logon(ircConfig.getNickName());
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            log.error("Thread Sleep error!",e);
        }
        this.ircConfig.getChannels().forEach(c->{
                join(c);
            });

            return true;
        }catch(Exception e){
            log.error("Reconnect error!",e);
          
        }
        return false;
    }

 

    private void startSubThreads(){
        List<IrcThread> ircThreads = this.context.getByType(IrcThread.class);
   
        for(IrcThread t : ircThreads){
            Thread thread = new Thread(t);
            thread.setName(t.getClass().getSimpleName());
            thread.start();
        }
       
    }

    public void start() throws IOException {
        
        this.init();
        log.info(String.format("Start to connect IRC server(Server:%s, Port:%s)",this.ircConfig.getServer(), this.ircConfig.getPort()));
        
        this.startSubThreads();

        log.info("Nick Name :" + ircConfig.getNickName() + " Channel List ：" + this.ircConfig.getChannels());
        this.logon(ircConfig.getNickName());
        try {
            Thread.sleep(3000);
        }catch (Exception e){
            log.error("Thread Sleep error!",e);
        }
        this.ircConfig.getChannels().forEach(c->{
                join(c);
            });

    }



    private void logon(String nick){

        IrcLogonSaslMessage saslLogonMsg = new IrcLogonSaslMessage();
        saslLogonMsg.setAuthId(this.ircConfig.getAuthId());
        saslLogonMsg.setNick(nick);
        saslLogonMsg.setUserName(this.ircConfig.getUserName());
        saslLogonMsg.setPwd(RunIrcClientBackEnd.saslPwd);
        saslLogonMsg.setHost("zm.test.cc");
        saslLogonMsg.setServer("1");
        saslLogonMsg.setRealName(this.ircConfig.getRealName());
        this.sendMessage(saslLogonMsg);
    }
    /**
     * Connect channel and return the channel object.
     * @param channelName
     * @return
     */
    public IrcChannel join(String channelName){
        IrcChannel channel = this.channels.get(channelName);
        boolean succeed = this.localMemoryMsgQueue.registerReceiveQueue(channelName);

        if( channel != null){
            log.warn("The channel already connected!" + channel);
            return channel;
        }
        if( !succeed){
            log.error("Msg queue register error!");
            //return null;
        }

        /**
         * do connect logic.
         */

        channel = this.context.getBean(IrcChannel.class) ;
        channel.init(channelName);
        channel.setLocalMemoryMsgQueue(this.localMemoryMsgQueue);
        this.channels.put(channelName,channel);
        this.channelList.add((channel));
        log.info("加入频道："  + channelName);
        IrcJoinMessage joinMsg = new IrcJoinMessage();
        joinMsg.setChannel(channelName);
        this.sendMessage(joinMsg);

        this.currentChannel = channel;
        return this.currentChannel;
    }


    public void part(String channelName){
        IrcChannel channel = this.channels.remove(channelName);

        if( channel == null){
            log.warn("The channel already connected!" + channel);
            return ;
        }

        for(Iterator<IrcChannel> it = this.channelList.iterator();it.hasNext();){
           IrcChannel c = it.next();
           if(channelName.equals(c.getChannelName())){
               it.remove();
           }
        }
        this.localMemoryMsgQueue.removeReceiveQueue(channelName);
        channel.part();
    }


    /**
     * 将消息添加到发送队列的末尾。
     * 实际发送时间，根据{@link MsgSendThread}工作情况而定。
     * @param msg
     */
    public void sendMessage(IrcSendMessage msg) {
        localMemoryMsgQueue.addSendQueue(msg);
    }

    public IrcReceiveMessage getSystemMsg(){
       return this.localMemoryMsgQueue.getSystemMsg();
    }

    public  void sendMessageDirect(String msg) throws Exception {
        synchronized(this.writer){
            try{
                this.writer.write(msg);
                this.writer.flush();
            }catch(Exception e){
                log.error("write error!",e);
                Thread.sleep(200);
            }
        }
    }


    public synchronized String readLine() throws Exception  {
        String msg = null;
        synchronized(this.reader){
            try{
                msg =  this.reader.readLine();
            }catch(Exception e){
                log.error("read error!",e);
                Thread.sleep(200);
            }
        }
        return msg;
    }

    public void switchToNextChannel() {
        if(CollectionUtils.isEmpty(this.channelList)){
            return;
        }
        if(currentChannelIndex >= this.channelList.size()){
            this.currentChannelIndex = 0;
        }
        this.currentChannel = this.channelList.get(currentChannelIndex);
        currentChannelIndex++;
    }

    public boolean isListenning(String channel){
        return this.channels.containsKey(channel);
    }
}
